package cn.zswltech.gruul.web.api.util;

import cn.zswltech.gruul.web.api.util.spring.SpringContextUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 密码非对称加密处理
 *
 * @author xuluquan
 * @date 2020-04-02 10:55
 */
public class TokenUtil {

    private static Logger logger = LoggerFactory.getLogger(TokenUtil.class);

    public static final String RSA_ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";

    //public static final int KEY_SIZE_2048 = 2048;
    public static final int KEY_SIZE_1024 = 1024;

    private static final String ALGORITHM = "RSA";
    /**
     * 给前端的公钥加密-密码
     */
    public static final String PWD_PUBLICKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNpMKIVmt0u5lx62tRD1O/15EyNLN0lNi3++ytnvLalkQNSrrqU2w3uD5NwdVE/v4OrDznTpBdTl6N1ryXAILU5GDu0bLATC46RKxDlH52LIvaRBU7BZkEGqllEqRJFmwtvtNCVeZD6ekJWc67MLUh4LNa1yMQ9V6Zsf64uY2lgwIDAQAB";
    /**
     * 后端私钥解密-密码，不能泄漏
     */
    public static final String PWD_PRIVATEKEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM2kwohWa3S7mXHra1EPU7/XkTI0s3SU2Lf77K2e8tqWRA1KuupTbDe4Pk3B1UT+/g6sPOdOkF1OXo3WvJcAgtTkYO7RssBMLjpErEOUfnYsi9pEFTsFmQQaqWUSpEkWbC2+00JV5kPp6QlZzrswtSHgs1rXIxD1Xpmx/ri5jaWDAgMBAAECgYEAqFDnrIHXd/cos/Z1oyZBbS86t3YtQAmojIaVhNIpgkIltx9od+0KEV0rGafCMLPpLAdL3iMOxGAlwkB1ob6YVcJyEfqgVwRQGaUzjfrIZggkYH6iOFnd7ELnoWpBgFjeuXUbAKxtJkkYwwTDcrBD1hcxN9boe8GJ5rlci0jykpkCQQDl3lB0ZWeWtybcgu87vpMCxaiwZ1qwnQpR9qzsxmVkeyXEf1NzkHZJwG1eWRVcCLRM0wHYovWFDdgRJ3wunGylAkEA5QVy9J3TjJCEnjHC9hsIaONdXnZ3z0l64Uz7q1plPysHh7DjN9lfPLvKrHCHj3N+3Qb8QnuR9n/0b0qt7ytpBwJAe+P4ROOqkyafgVXojrDGF0ZlBr6NuIbLWyWySeMcHq3oyyE83xpLRrC5wZIN2w6t7oIDirk5zr6JZ6V6XrzcKQJBAK6BrWtJC1YTFIo53pWKWakafh2pZXd0RQaBk06o7Xbpga5aEXiP0GrFqRQmFpUu6x7d4L+Dyt+p9gYLQGd6i2cCQHz3P4GsMtp/Sq0C3UaDfk6jZKUvPKnTq38ENdyQaKdyXxJoLuoL+ZebM2zIzRgY1VPqk/aoleO0UmChtBLUXUg=";

    /**
     * CRSF token 公钥
     */
    public static final String CSRF_TOKEN_PUBLICKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxhM08dq4BRb5P89i6wCrT9mKVncxSMMEO/tat4kH7rgms7DX3kXKlh69ycLnUHEX7XFP2FBvC+gOGofQqVXYg4ujqxLM1BuiIhh0ldvwDNreERaRto6hmuBhbyA/6f89W28axledH2HMKb4EVi4CziV8s1PTD6Gy/EBhbpjReNQIDAQAB";

    /**
     * CRSF token 私钥，不能泄漏
     */
    public static final String CSRF_TOKEN_PRIVATE = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALGEzTx2rgFFvk/z2LrAKtP2YpWdzFIwwQ7+1q3iQfuuCazsNfeRcqWHr3JwudQcRftcU/YUG8L6A4ah9CpVdiDi6OrEszUG6IiGHSV2/AM2t4RFpG2jqGa4GFvID/p/z1bbxrGV50fYcwpvgRWLgLOJXyzU9MPobL8QGFumNF41AgMBAAECgYEAsXkDdp9v9vWy/lvbIVldd+urQ8kurDzxxfwgxFafgO7N3YOJsKhI5hRw4wivv89EZ7g2eJ5B4vvw6nifknM+VJbQWZbZHY5uisSrgUOp4Wjysw/nMpRACXW22FZFVe7eFlboqxaivJPgfUBstOlkkURER35HWO07rBqC8r3dSAECQQDnjrZi3FYzz4gG4GlgV/AHRfgMWrNCiMvmwcKw9baiQ66mQ7LPtG+QVrXS6sy2VXNHksuGHLcCA5+nfsvbbPthAkEAxEHSdIVVUvTlCk8nTixmodSWZ1JYb43kACk4M+5k62iAzg2Fb0+W3WiYP+aIgwyjry2roO5XFneG+OOHKW1HVQJAR/cCmBcCc3e8Pubf2SF7Z8dHIA/QRo1dUYpGh9jVufhu/x1rJUJeqe1I9RQkK0bDXSsyNawW9HlL1TKDCN8GAQJBAKgwUI+TBIBHZ1Hia6jO3FWrvgllxGw1iUOqD4RRtSMZN+leJRAzgPLWBEQe6LaGSnCCDyenmfKjFISPWPHjuNECQB0bi/J03JJG3+lbgKmacfZkjz/CuRiMfuLnkmzZFvUchdQIz4b5t1cUpKvuf/nl9dmeJqzJYOQuf6U45nAcBlI=";

    public static KeyPair generateKeyPair() {
        return generateKeyPair(KEY_SIZE_1024);
    }

    public static KeyPair generateKeyPair(int keySize) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
            keyPairGenerator.initialize(keySize);
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("Failed to generate key pair!", e);
        }
    }

    public static PublicKey getPublicKey(String base64PublicKey) {
        try {
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(base64PublicKey));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            return publicKey;
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to get public key!", e);
        }
    }

    public static PublicKey getPublicKey(BigInteger modulus, BigInteger exponent) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
            return keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to get public key!", e);
        }
    }

    public static String getBase64PublicKey(PublicKey publicKey) {
        return Base64.encodeBase64String(publicKey.getEncoded());
    }

    public static PrivateKey getPrivateKey(String base64PrivateKey) {
        try {
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(base64PrivateKey));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            return privateKey;
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to get private key!", e);
        }
    }

    public static PrivateKey getPrivateKey(BigInteger modulus, BigInteger exponent) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(modulus, exponent);
            return keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to get private key!", e);
        }
    }

    public static String getBase64PrivateKey(PrivateKey privateKey) {
        return Base64.encodeBase64String(privateKey.getEncoded());
    }

    public static byte[] encryptAsByteArray(String data, PublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data.getBytes("UTF-8"));
        } catch (Exception e) {
            throw new IllegalArgumentException("Encrypt failed!", e);
        }
    }

    public static byte[] encryptAsByteArray(String data, String base64PublicKey) {
        return encryptAsByteArray(data, getPublicKey(base64PublicKey));
    }

    public static String encryptAsString(String data, PublicKey publicKey) {
        return Base64.encodeBase64String(encryptAsByteArray(data, publicKey));
    }

    public static String encryptAsString(String data, String base64PublicKey) {
        return Base64.encodeBase64String(encryptAsByteArray(data, getPublicKey(base64PublicKey)));
    }

    public static String decrypt(byte[] data, PrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(cipher.doFinal(data), "UTF-8");
        } catch (Exception e) {
            throw new IllegalArgumentException("Decrypt failed!", e);
        }
    }

    public static String decrypt(byte[] data, String base64PrivateKey) {
        return decrypt(data, getPrivateKey(base64PrivateKey));
    }

    public static String decrypt(String data, PrivateKey privateKey) {
        return decrypt(Base64.decodeBase64(data), privateKey);
    }

    public static String decrypt(String data, String base64PrivateKey) {
        return decrypt(Base64.decodeBase64(data), getPrivateKey(base64PrivateKey));
    }

    public static boolean validateCSRFToken(HttpServletRequest request) {
        String path = request.getRequestURI();
        return validateCSRFToken(RequestUtils.getRequestParams(request), path);
    }

    public static boolean validateCSRFToken(Map<String, String> params, String path) {

        try {
            if (StringUtils.isNotBlank(path) && path.contains("logout")) {
                return true;
            }

            String encryptCSRFToken = params.get(RequestUtils.X_CF_RANDOM);
            String referer = params.get(RequestUtils.REFERER);
            String originServer = params.get(RequestUtils.ORIGIN_SERVER);
            if (StringUtils.isBlank(encryptCSRFToken) || "null".equalsIgnoreCase(encryptCSRFToken)) {
                logger.error("X-Cf-Random校验为空");
                return false;
            }

            String wlToken = params.get(CookieUtils.WL_TOKEN);

            if (StringUtils.isBlank(referer)) {
                logger.error("refer为空");
                return false;
            }
            List<String> servers = new ArrayList<>(Arrays.asList(originServer.split(";|,")));
            //合法服务器配置
            String validServer =null;// SpringContextUtil.getApplicationContext().getEnvironment().getProperty("system.request.referer.servers");
            if (StringUtils.isNotBlank(validServer)) {
                String[] validServers = validServer.split(";|,");
                servers.addAll(Arrays.asList(validServers));
            }
            //判断referer是否在合法服务器列表中，referer和请求服务器必须同源
            boolean find = servers.stream().anyMatch(s -> referer.contains(s));
            if (!find) {
                logger.error("refer校验失败，refer={},originServer={}", referer, StringUtils.join(servers, ";"));
                return false;
            }
            //解密后csrfToken如：abc|1|5，其中abc是真正的csrf token，其值随机变化
            String csrfTokenMix = decrypt(encryptCSRFToken, CSRF_TOKEN_PRIVATE);
            String[] csrfs = csrfTokenMix.split("\\|");
            int start = Integer.parseInt(csrfs[csrfs.length - 2]);
            int end = Integer.parseInt(csrfs[csrfs.length - 1]);
            List<String> csrfsList = new ArrayList<>(Arrays.asList(csrfs));
            csrfsList.remove(String.valueOf(start));
            csrfsList.remove(String.valueOf(end));
            String crsfToken = String.join("|", csrfsList);
            if (!crsfToken.equals(wlToken.substring(start, end))) {
                logger.error("X-Cf-Random校验失败，可能被篡改");
                return false;
            }
        } catch (Exception e) {
            logger.warn("csrf处理异常,msg={}", e.getMessage(),e );
            return false;
        }
        return true;
    }


    /**
     * token范围内随机字符串
     *
     * @param token
     * @return java.lang.String
     * @author xuluquan
     * @date 2020-04-09 19:41
     */
    public static String getCRSFToken(String token) {
        int start = getRandom(1, 50);
        int end = getRandom(51, 100);
        return String.format("%s|%s|%s", token.substring(start, end), start, end);
    }

    /**
     * 范围内随机数字
     *
     * @param start
     * @param end
     * @return int
     * @author xuluquan
     * @date 2020-04-09 19:41
     */
    private static int getRandom(int start, int end) {
        return (int) (Math.random() * (end - start + 1) + start);
    }


    public static void main(String[] args) {

        KeyPair keyPair = generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();

        String publickey = Base64.encodeBase64String(keyPair.getPublic().getEncoded());
        //System.out.println(getBase64PublicKey(keyPair.getPublic()));
        String data = "111";
        String enctryData = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzNzM2ODFDQUM5M0U2NTgwNzZDRDk5RkNBQkFDQkEyRSIsInN1YiI6InhscSIsImlzcyI6ImZyYXVkbWV0cml4IiwiaWF0IjoxNTg1ODc5OTkxLCJleHAiOjE1ODY0ODQ3OTF9.tIQUlbsP-A7AqinNocwg08yCELASqUnf-TI02jBCadQ";

        enctryData = enctryData.substring(0, 117);
        String encryStr = encryptAsString(enctryData, keyPair.getPublic());
        //encryStr="lSQJYJghALrdz2pdI3NZ7LkTqWrsgHJO7VrfX4/rxF1ym0+CGhz+vMXZICPD2wXlwsGBODtH9D2ZKWuvZvW0PCV6xZfA006yRwV3o4xL0pM0c5oxTMypxs5BXahZ8faf4Z6DqhZMz5geunhUEKUFim7HMBHmtNfnpUuFA0ycw3A=";
        //PrivateKey privateKey2=getPrivateKey("MIICXQIBAAKBgQCrdg/w/HzlRBUqD+cnEsFPkdneNgu9dn2YtbTqEO3lXOxfxxoOW5+o9MFS0lIPKhFNrvfkf2uADYfXOJx/5GRFe8K2L5Xt9ZiIfaisRwVYn3x+qFlxnULlvFAB4serFV2bcTMrLHk3nrfgbm3PC/p9yrsjHDEesg+mX/DaVbInbwIDAQABAoGAQRLw3+xj1nutcKZSe4DhvzpCrtRiRkj3SAMGo6NDNr66s2sUV4isouopiS5rmHfF027phF5bxWDdQnCo7N0YtKsSbVwkaLSOkr9vUcok9B8sAHnWYsvKWFAurWU49tekpsgcc2rsXOnMiIXLUvxTZMFXq4EiTyIZKP1uMi7QGUkCQQDcpbyk8Vu+4T8aCdQXjc+YtHmbOGS0CjfqHPauq0pU0j/xakjDZAGQWQ2iWiFjjvp/ijZStouco0Q+rUXC4NSjAkEAxu7cY1GDlBWvM0mHh8G9abWhjBC1xlRAPnbKu9Td0VYXKSw4WOKTJe9lVdnZKBzJTfc1F6PtGynXwR8w2fnCxQJBANYGZc/Q1l92+dB2DCa0xdOn6/VmZDhHWSlb4uRW/oj8UE4J/H6a+PFdkF4U8yIxREaGeF8O9ql/rOjnBi4cC+UCQDWJ95prR8RFCb/93BH3XGjkzzOJE8LqzdeB1RI0Ofaq+tTD5+5UHFIrWdR/KuiWxr7K92kDXz7eRgjdwiIIDBkCQQC0lqRckZXC11L11IO3v8VRpdgzyyH6illXQZrtO0aoW4zKOQ80hTTm1YrimG+mwOgW48kvoDTpT1ogm/xUp5jr");
        //System.out.println(encryStr);
        System.out.println(publickey);
        System.out.println(getBase64PrivateKey(privateKey));

        //String descData = decrypt(encryStr, PWD_PRIVATEKEY);
        // System.out.println(descData);

    }


}
